//////////////////////////////////////////////////////////////////////////////////
//	Copyright 2011 by Yosef Grabivker, ryuho@homenetworksoft.com
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//////////////////////////////////////////////////////////////////////////////////

#include <wx/wx.h>
#include <wx/dcbuffer.h>
#include <wx/pen.h> 
#include "levelgraph.h"

#define NET_MAX_SPEED 			100
#define MAX_STORED_POINTS		24*60*60/30

#define LEFT_SPACER				40
#define GRAPH_SPACER			20
#define TIME_LABEL_GAP			4
#define TRAFIC_LABEL_GAP		4
#define UP_SPACER				15

#define DEF_TIME_PERIOD			120

#define MIN_BLOCK_SIZE			10
#define MIN_TIME_BLOCK_SIZE		45

#define INIT_Y_VAL_MAX			10

static int nTrafBlocks[] = {10,5,2,1};
static int nTimeBlocks[] = {10,5,3,2,1};

CLevelGraph::CLevelGraph(wxWindow* parent, cGraphMode cGraphMode, cGraphLabelMode cLabelMode, wxWindowID id ):
	wxPanel(parent, id,wxDefaultPosition, wxSize(200,200), wxTAB_TRAVERSAL),
	m_cGraphMode(cGraphMode),	m_timer(this), m_nUpdateTime(1000),
	m_nTimePeriodSec(DEF_TIME_PERIOD), m_strMesureItemName( wxT("db") ),
	m_pGetSizezFrom(NULL)
{
	SetBackgroundStyle(wxBG_STYLE_CUSTOM);
	
	Connect(wxEVT_PAINT, wxPaintEventHandler(CLevelGraph::OnPaint));
	Connect(wxEVT_SIZE, wxSizeEventHandler(CLevelGraph::OnSize));
	Connect(wxEVT_TIMER, wxTimerEventHandler(CLevelGraph::OnTimer));
	
	if ( !m_timer.IsRunning() )
		m_timer.Start(m_nUpdateTime); //One per second

	SetMinSize(wxSize(200,200));

	m_cLabelMode = cLabelMode;
}

CLevelGraph::~CLevelGraph()
{
	if ( m_timer.IsRunning() )
		m_timer.Stop();
}

//Add only if value is not zero
void CLevelGraph::SetDataEx(const wxString& strName, unsigned long long  lVal, time_t tEvent)
{
	try
	{
		TRAFFICDATASTORAGE::iterator iCur = m_lstGraphData.find(strName);

		if ( iCur != m_lstGraphData.end() )
		{
			AddData( iCur->second, lVal, tEvent );
		}
		else
		{
			if ( lVal )
			{
				TRAFFICDATA lstData;
				AddData( lstData, lVal, tEvent );
				m_lstGraphData[strName] = lstData; 
			}
		}
	}
	catch(...)
	{
	}
}

void CLevelGraph::SetData(const wxString& strName,
						  unsigned long long  lVal, time_t tEvent)
{
	try
	{
		TRAFFICDATASTORAGE::iterator iCur = m_lstGraphData.find(strName);

		if ( iCur != m_lstGraphData.end() )
		{
			AddData( iCur->second, lVal, tEvent );
		}
		else
		{
			TRAFFICDATA lstData;
			AddData( lstData, lVal, tEvent );
			m_lstGraphData[strName] = lstData; 
		}
	}
	catch(...)
	{
	}
}

void CLevelGraph::SetUpdateInterVal(unsigned int nUpdateTime)
{
	if ( m_timer.IsRunning() )
		m_timer.Stop();
	
	m_nUpdateTime = nUpdateTime;
	
	m_timer.Start(m_nUpdateTime);
}

void CLevelGraph::OnTimer(wxTimerEvent& event)
{
	Refresh();	
}

void CLevelGraph::SetColor(const wxString& strName, const wxColor& cl)
{
	try
	{
		m_mapColor[strName]=cl;
	}
	catch(...)
	{
	}

	Refresh();
}

wxColor CLevelGraph::GetColor(const wxString& strName)
{
	wxColor clr(0,0,0);

	try
	{
		COLORMAP::const_iterator iCur = m_mapColor.find(strName);

		if ( iCur != m_mapColor.end() )
		{
			clr = iCur->second;
		}
	}
	catch(...)
	{
	}
	
	return clr;
}

void CLevelGraph::SetZones(const ZONELIST& lstZone)
{
	m_lstZone = lstZone;

	Refresh();
}

void CLevelGraph::OnSize(wxSizeEvent& event)
{
	Refresh();
}

void CLevelGraph::OnPaint(wxPaintEvent& event)
{
	wxSize szMe = GetClientSize();
	wxBufferedPaintDC dc(this);

	wxBrush br(GetBackgroundColour());

	//Clear
	dc.SetBackground(br);
	dc.Clear();

	unsigned long lDataMax = GetMaxDataVal();
	
	wxRect rectGraphArea;
	DrawGrid( dc, lDataMax,  rectGraphArea );

	rectGraphArea.Deflate(1,1);

	for( TRAFFICDATASTORAGE::const_iterator iCur = m_lstGraphData.begin();
		iCur != m_lstGraphData.end();++iCur )
	{
		const TRAFFICDATA& data = iCur->second;
		wxColor color = GetColor(iCur->first);

		if (cNormalGraphMode == m_cGraphMode )
		{
			DrawGraph(dc, data, rectGraphArea, color);
		}
		else if (cGrowGraphMode == m_cGraphMode )
		{
			DrawDiffGraph(dc, data, rectGraphArea, color);
		}
		else if (cGrowBarGraphMode == m_cGraphMode )
		{
			DrawDiffGraphBar(dc, data, rectGraphArea, color);
		}
	}

	DrawGridLines(dc, rectGraphArea.x, rectGraphArea.y, rectGraphArea.width, rectGraphArea.height);
}

void CLevelGraph::CalculateBlockParams( int nLineLength, int* nBlocksMap, int nMapSize,
										 int nBlockMinSize, int &nBlockSize, int& nBlockCount )
{
	nBlockCount = 1;
	nBlockSize  = nLineLength;

	for (int i=0;i<nMapSize;i++)
	{
		if ( nLineLength/nBlocksMap[i]>=nBlockMinSize )
		{
			nBlockCount = nBlocksMap[i];
			nBlockSize  = nLineLength/nBlocksMap[i];
			break;
		}
	}
}

void CLevelGraph::DrawGrid( class wxBufferedPaintDC& dc, unsigned int lDataMax,
		wxRect&  rectGraphArea)
{
	wxSize szMe = GetClientSize();
	int nLeftSpacer = m_pGetSizezFrom?m_pGetSizezFrom->GetGraphLeftWidth():LEFT_SPACER;

	int xStart = nLeftSpacer;
	int yStart = UP_SPACER;
	int w = szMe.GetWidth()-nLeftSpacer-GRAPH_SPACER;
	int h = szMe.GetHeight();

	wxFont  font(8, wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_NORMAL|wxFONTFLAG_ANTIALIASED);
	dc.SetFont(font);

	int wText, hText;
	dc.GetTextExtent(wxT("R"), &wText, &hText);

	h-=2*hText+4;

	DrawLegend(dc, w, h);

	//w-=GRAPH_SPACER;

	CalculateBlockParams(h, nTrafBlocks,sizeof(nTrafBlocks)/sizeof(nTrafBlocks[0]),
		MIN_BLOCK_SIZE, m_nTrafBlockSize, m_nTrafBlockCount);

	CalculateBlockParams(w, nTimeBlocks,sizeof(nTimeBlocks)/sizeof(nTimeBlocks[0]),
		MIN_TIME_BLOCK_SIZE, m_nTimeBlockSize, m_nTimeBlockCount);

	//Correct Height
	h=m_nTrafBlockCount*m_nTrafBlockSize;
	/*yStart = (szMe.GetHeight()-h)/2;*/
	yStart = UP_SPACER;

	//Correct Width
	w=m_nTimeBlockCount*m_nTimeBlockSize;

	rectGraphArea.x = xStart;
	rectGraphArea.y = yStart;
	rectGraphArea.width  = w;
	rectGraphArea.height = h;

	//Draw BOX
	wxBrush br(*wxWHITE);
	dc.SetBrush(br);

	wxPen   pn(*wxBLACK);
	dc.SetPen(pn);

	dc.DrawRectangle(rectGraphArea);

	DrawTimeLabels(dc,rectGraphArea.x, 0 , rectGraphArea.width, 0, true );
	DrawTimeLabels(dc,rectGraphArea.x, rectGraphArea.y , rectGraphArea.width, rectGraphArea.height, false);

	DrawTraphicLabels(dc, rectGraphArea.x, rectGraphArea.y , rectGraphArea.width, rectGraphArea.height, lDataMax);

	wxRect rc(rectGraphArea);
	rc.Deflate(1,1);

	DrawZones(dc, rc.x, rc.y , rc.width, rc.height, lDataMax, rectGraphArea);
}

void CLevelGraph::DrawLegend(  wxBufferedPaintDC& dc, int&  wAll, int&  hAll)
{
	wxSize szMe = GetClientSize();

	int w(0);
	int h(0);
	int wBox(0);

	if ( m_cLabelMode ==cGraphLabelRight )
	{
		DrawLegendRight(dc, 0, 0, w, h, wBox, false);

		if ( w>5 )
		{
			wAll-=w-15;

			int x = szMe.x-w;
			int y = (szMe.y - h)/2;

			DrawLegendRight(dc, x, y, w, h, wBox, true);
		}
	}
	else
	{
		//DrawLegend(  dc, LEFT_SPACER, w, h, true );
		//hAll-=h;

		DrawLegendBottom(  dc, 0, w, h, false );


		//if ( w<szMe.x )
		{
			int xStart = (szMe.x - w)/2;
			int nLeftSpacer = m_pGetSizezFrom?m_pGetSizezFrom->GetGraphLeftWidth():LEFT_SPACER;
			//int xStart = nLeftSpacer;

			if (xStart<0 ) xStart = 0;

			DrawLegendBottom(  dc, xStart, w, h, true );

			hAll-=h;
		}
	}
}

void CLevelGraph::DrawLegendRight(  wxBufferedPaintDC& dc, int x, int y,
							 int& wTotal, int& hTotal, int& wBoxCalc, bool bDraw )
{
	wxSize szMe = GetClientSize();

	int wTextLabel, hTextLabel;
	int wTextVal, hTextVal;

	const int nItemBorder= 5;
	const int nBorder	= 2;
	const int nLineW	= 5;
	const int nLineH	= 3;
	const int nBoxPenW	= 1;

	wxFont  font(8, wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_BOLD|wxFONTFLAG_ANTIALIASED);
	dc.SetFont(font);

	int xStart = x;
	int yStart = y;

	for( TRAFFICDATASTORAGE::const_iterator iCur = m_lstGraphData.begin();
			iCur != m_lstGraphData.end();++iCur )
	{
		const TRAFFICDATA& data = iCur->second;
		wxColor color = GetColor(iCur->first);

		unsigned long long lVal = 0;
		if (cNormalGraphMode == m_cGraphMode )
		{
			if ( !data.empty() )	lVal = data.front().data;
		}
		else
		{
			if ( data.size()>=3)
			{
				TRAFFICDATA::const_iterator iTmp = data.begin();
				const TRAFFICDATAENTRY& entry1 = *iTmp;
				iTmp++;
				const TRAFFICDATAENTRY& entry2 = *iTmp;

				int x, y;

				if ( GetDistance( entry1, entry2, x,  y  ) )
				{
					lVal = y;
				}
			}
		}

		dc.GetTextExtent(iCur->first, &wTextLabel, &hTextLabel);
		wxString strVal = wxString::Format(wxT("%Lu"), lVal );

		dc.GetTextExtent(strVal, &wTextVal, &hTextVal);

		xStart = x;

		//1. Draw Box
		xStart+= 0;

		wxBrush br( *wxWHITE );
		wxPen   pn(color, nBoxPenW);

		dc.SetBrush(br);
		dc.SetPen(pn);

		int xBox = xStart;
		int yBox = yStart;
		int wBox = nBoxPenW+nBorder+nLineW+nBorder+wTextVal+nBorder+nLineW+nBorder+nBoxPenW;
		int hBox = hTextLabel;

		if ( wBox>wBoxCalc )
		{
			wBoxCalc=wBox;
		}
		else
		{
			wBox = wBoxCalc;
		}

		if ( bDraw )
		{
			dc.DrawRectangle(xBox, yBox, wBox, hBox );
		}

		//2. Draw Line
		wxPen   pnLine(color, nLineH);
		dc.SetPen(pnLine);

		xStart+=nBoxPenW+nBorder;

		int nYLine = yBox+(hBox-nBoxPenW)/2;

		if ( bDraw )
		{
			dc.DrawLine(xStart, nYLine, xStart+nLineW, nYLine);
		}

		xStart+=nLineW+nBorder;

		//3. Draw Value
		int nYText = yBox+(hBox-hTextLabel)/2;

		if ( bDraw )
		{
			dc.DrawText(strVal, xStart, nYText); 
		}

		xStart+=wTextVal;

		//4. Draw Line
		xStart=xBox+wBox-nLineW-2*nBorder;

		if ( bDraw )
		{
			dc.DrawLine(xStart, nYLine, xStart+nLineW, nYLine);
		}

		xStart+=nLineW+5;

		//5. Draw Text Label
		if ( bDraw )
		{
			dc.DrawText(iCur->first, xStart, nYText); 
		}

		xStart+=nBorder+wTextLabel;

		wTotal = wTotal>xStart-x?wTotal:xStart-x;

		yStart+=hBox+nItemBorder;
	}

	hTotal = yStart-y;
}


void CLevelGraph::DrawLegendBottom(  wxBufferedPaintDC& dc, int x,
							 int& wTotal, int& hTotal, bool bDraw )
{
	wxSize szMe = GetClientSize();

	int wTextLabel, hTextLabel;
	int wTextVal, hTextVal;

	const int nBorder = 1;
	const int nLineW = 5;
	const int nLineH = 2;
	const int nBoxPenW = 1;

	wxFont  font(8, wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_LIGHT|wxFONTFLAG_ANTIALIASED);
	dc.SetFont(font);

	int xStart = x;

	for( TRAFFICDATASTORAGE::const_iterator iCur = m_lstGraphData.begin();
			iCur != m_lstGraphData.end();++iCur )
	{
		const TRAFFICDATA& data = iCur->second;
		wxColor color = GetColor(iCur->first);

		unsigned long long lVal = 0;
		if ( !data.empty() )	lVal = data.front().data;

		dc.GetTextExtent(iCur->first, &wTextLabel, &hTextLabel);
		wxString strVal = wxString::Format(wxT("%Lu"), lVal );

		dc.GetTextExtent(strVal, &wTextVal, &hTextVal);

		hTotal = nBorder+nBoxPenW+nBorder+hTextVal+nBorder+nBoxPenW+nBorder;

		//1. Draw Box
		xStart+=nBorder+10;

		wxBrush br(/*GetBackgroundColour()*/*wxWHITE);
		wxPen   pn(color, nBoxPenW);

		dc.SetBrush(br);
		dc.SetPen(pn);

		int xBox = xStart;
		int yBox = szMe.y-(nBorder+nBoxPenW+nBorder+hTextVal+nBorder+nBoxPenW);
		int wBox = nBoxPenW+nBorder+nLineW+nBorder+wTextVal+nBorder+nLineW+nBorder+nBoxPenW;
		int hBox = hTextLabel;

		if ( bDraw &&
			 xStart-x+wBox+nBorder+wTextLabel+5>szMe.x )
		{
			break;
		}


		if ( bDraw )
		{
			dc.DrawRectangle(xBox, yBox, wBox, hBox );
		}

		//2. Draw Line
		wxPen   pnLine(color, nLineH);
		dc.SetPen(pnLine);

		xStart+=nBoxPenW+nBorder;

		int nYLine = yBox+(hBox-nBoxPenW)/2;

		if ( bDraw )
		{
			dc.DrawLine(xStart, nYLine, xStart+nLineW, nYLine);
		}

		xStart+=nLineW+nBorder;

		//3. Draw Value
		int nYText = yBox+(hBox-hTextLabel)/2;

		if ( bDraw )
		{
			dc.DrawText(strVal, xStart, nYText); 
		}

		xStart+=wTextVal;

		//4. Draw Line
		xStart+=nBorder;

		if ( bDraw )
		{
			dc.DrawLine(xStart, nYLine, xStart+nLineW, nYLine);
		}

		xStart+=nLineW+10;

		//5. Draw Text Label
		if ( bDraw )
		{
			dc.DrawText(iCur->first, xStart, nYText); 
		}

		xStart+=nBorder+wTextLabel+5;
	}

	wTotal = xStart;
}


void CLevelGraph::DrawZones(class wxBufferedPaintDC& dc, int x, int y, int w, int h,
							unsigned int lDataMax, const wxRect&  rectGraphArea)
{
	unsigned int lUpLevel  = CalculateUpLevel( lDataMax );
	double lPerUnit  = double(h)/lUpLevel;

	lPerUnit=lPerUnit?lPerUnit:1;

	ZONELIST::const_iterator iCur;

	wxRect rect(rectGraphArea);
	dc.SetClippingRegion(rect.Deflate(1,1) );

	for( iCur=m_lstZone.begin();iCur!=m_lstZone.end();++iCur )
	{
		const ZONERANGE& entry = *iCur;

		//Draw BOX
		wxBrush br(entry.color);
		wxPen   pn(entry.color);
		dc.SetBrush(br);
		dc.SetPen(pn);

		int yFrom = y +h - lPerUnit*entry.nFrom;
		int yTo   = y +h - lPerUnit*entry.nTo;

		if ( yFrom>0 )
		{
			if ( yTo<0 )
				yTo=y;

			dc.DrawRectangle(x, yTo, w, yFrom-yTo);
		}
	}

	dc.DestroyClippingRegion();
}


//Vertical labels
void CLevelGraph::DrawTraphicLabels( class wxBufferedPaintDC& dc, int x, int y, int w, int h,
									 unsigned int lDataMax )
{
	m_lUpLevel  = CalculateUpLevel( lDataMax );
	unsigned int lPerBlock = m_lUpLevel/m_nTrafBlockCount;


	int wText, hText;
	
	wxFont  font(8, wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_LIGHT|wxFONTFLAG_ANTIALIASED);

	dc.SetFont(font);

	for (int i=0;i<=m_nTrafBlockCount;i++)
	{
		int yTxtCenter = y +h - i*m_nTrafBlockSize;

		wxString strLabel = FormatLabel(i*lPerBlock, lDataMax);

		dc.GetTextExtent(strLabel, &wText, &hText);

		int yTxt = yTxtCenter-hText/2;
		int xTxt = x-wText-TRAFIC_LABEL_GAP;

		dc.DrawText( strLabel, xTxt, yTxt );
	}
}

wxString CLevelGraph::FormatTime(int nTimeBlockIndex, bool bSimple)
{
	time_t tNow = time(NULL);
	int nTimePerBlock = m_nTimePeriodSec/m_nTimeBlockCount;

	struct Format {
		unsigned long		factor;
		const wxChar*		string;
	};

	const Format f[]=
	{
		{1,		_("sec") },
		{60,	_("min") },
		{60*60, _("hours") } 
	};

	unsigned long		factor=1;
	const wxChar*		strTimeLbl=NULL;

	for(int i=0;i<sizeof(f)/sizeof(f[0]);i++)
	{
		if ( nTimePerBlock/f[i].factor<60 )
		{
			factor = f[i].factor;
			strTimeLbl = f[i].string;
			break;
		}
	}

	wxString strLabel;

	if ( bSimple )
	{
		strLabel = wxString::Format(wxT("-%u"), (nTimeBlockIndex*nTimePerBlock)/factor );
	}
	else
	{
		time_t tPast = tNow - nTimeBlockIndex*nTimePerBlock;

		wxDateTime dt(tPast);
		strLabel = dt.Format( wxT("%X") );
		int nPos = strLabel.rfind(wxT(":"));
	
		if (wxNOT_FOUND!=nPos)
		{
			strLabel.erase(nPos, strLabel.size() );
		}
	}

	if (!nTimeBlockIndex)
	{
		if ( bSimple )
		{
			strLabel=wxT("");
		}
		else
		{
			strLabel=_("now");
		}
	}

	if (nTimeBlockIndex==m_nTimeBlockCount)
	{
		if ( bSimple )
		{
			//strLabel+=wxT(" ");
			strLabel+=strTimeLbl;
		}
	}

	return strLabel;
}


//Horizontal labels
void CLevelGraph::DrawTimeLabels(class wxBufferedPaintDC& dc, int x, int y, int w, int h, bool bBottom)
{
	int wText, hText;
	int yLab = bBottom?0:y+h+TIME_LABEL_GAP;
	wxFont  font(8, wxFONTFAMILY_DEFAULT,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_NORMAL|wxFONTFLAG_ANTIALIASED);

	dc.SetFont(font);

	for (int i=0;i<=m_nTimeBlockCount;i++)
	{
		int xTxtCenter = x +w - i*m_nTimeBlockSize;

		wxString strLabel = FormatTime(i, bBottom);

		dc.GetTextExtent(strLabel, &wText, &hText);

		int xTxt = xTxtCenter-wText/2;

		if (i==m_nTimeBlockCount)
		{
			xTxt = xTxtCenter;
		}

		dc.DrawText( strLabel, xTxt, yLab);
		//dc.DrawRotatedText (strLabel,  xTxt, yLab, 120 );
	}
}

void CLevelGraph::DrawGridLines(class wxBufferedPaintDC& dc, int x, int y, int w, int h)
{
	wxPen pen(*wxBLACK,1,wxDOT);
	dc.SetPen(pen);

	//Horizontal blocks
	for (int i=1;i<m_nTrafBlockCount;i++)
	{
		int yBl = y+h - i*m_nTrafBlockSize;

		dc.DrawLine(x,yBl, x+w,yBl);
	}

	//Vert Blocks
	for (int i=1;i<m_nTimeBlockCount;i++)
	{
		int xBl = x+w - i*m_nTimeBlockSize;

		dc.DrawLine(xBl,y, xBl,y+h);
	}
}


void CLevelGraph::DrawDiffGraph(class wxBufferedPaintDC& dc, const TRAFFICDATA& data,
								const wxRect& rect, const wxColor& color)
{
	dc.DestroyClippingRegion();
	dc.SetClippingRegion(rect.Deflate(1,1) );

	wxSize szMe = GetClientSize();

	time_t tStart;

	wxPen   pen(color,3);
	wxBrush br(color);

	dc.SetPen(pen);
	dc.SetBrush(br);

	if ( data.size()>=2)
	{
		tStart = data.begin()->t;

		TRAFFICDATA::const_iterator iTmp;

		std::vector<wxPoint> list;

		for ( TRAFFICDATA::const_iterator iCur = data.begin();
				iCur!=data.end();iCur++ )
		{
			iTmp=iCur;

			const TRAFFICDATAENTRY& entry1 = *iTmp;
			iTmp++;  if (iTmp==data.end()) break;

			const TRAFFICDATAENTRY& entry2 = *iTmp;
			iTmp++;  if (iTmp==data.end()) break;

			int x, y;

			if ( !GetDistance( entry1, entry2, x,  y  ) ) continue;

			float fTimeDist  = tStart-x;

			fTimeDist/=m_nTimePeriodSec;

			int xs  =rect.x+rect.width- fTimeDist*rect.width;

			if (xs<0)
			{
				xs = 0;
			}

			float fTrafDist  = m_lUpLevel-y;

			fTrafDist/=m_lUpLevel;

			y   = rect.y+ fTrafDist*rect.height; 

			list.push_back( wxPoint(xs,y)   ); 

			if ( tStart-entry1.t>m_nTimePeriodSec )
				break;
		}

		if ( list.size()>2 )
		{
			std::vector<wxPoint>::iterator iCur;
			wxPoint* points = new wxPoint[list.size()];

			int i=0;
		
			for (iCur=list.begin();iCur!=list.end();iCur++, i++)
			{
				points[i].x = iCur->x;
				points[i].y = iCur->y;
			}
			
			dc.DrawSpline( list.size(), points  );
		}
	}
}

void CLevelGraph::DrawDiffGraphBar(class wxBufferedPaintDC& dc, const TRAFFICDATA& data,
								const wxRect& rect, const wxColor& color)
{
	dc.DestroyClippingRegion();
	dc.SetClippingRegion(rect.Deflate(1,1) );

	wxSize szMe = GetClientSize();

	time_t tStart;

	int nPen(1);

	if ( m_nTimePeriodSec<60) //1 min
	{
		nPen = 14;
	}
	else if ( m_nTimePeriodSec<300) //5 min
	{
		nPen = 12;
	}
	else if ( m_nTimePeriodSec<600) //10 min
	{
		nPen = 10;
	}
	else if ( m_nTimePeriodSec<1800) //30 min
	{
		nPen = 8;
	}
	else if ( m_nTimePeriodSec<3600) //1 h
	{
		nPen = 6;
	}
	else if ( m_nTimePeriodSec<=14400) //4 h
	{
		nPen = 2;
	}
	else
	{
		nPen = 1;
	}

	wxPen   pen(color, nPen );
	wxBrush br(color);

	dc.SetPen(pen);
	dc.SetBrush(br);

	if ( data.size()>=2)
	{
		tStart = data.begin()->t;

		TRAFFICDATA::const_iterator iTmp;

		for ( TRAFFICDATA::const_iterator iCur = data.begin();
				iCur!=data.end();iCur++ )
		{
			iTmp=iCur;

			const TRAFFICDATAENTRY& entry1 = *iTmp;
			iTmp++;  if (iTmp==data.end()) break;

			const TRAFFICDATAENTRY& entry2 = *iTmp;
			iTmp++;  if (iTmp==data.end()) break;

			int x, y;

			if ( !GetDistance( entry1, entry2, x,  y  ) ) continue;

			if (!y) continue;

			float fTimeDist  = tStart-x;

			fTimeDist/=m_nTimePeriodSec;

			int xs  =rect.x+rect.width- fTimeDist*rect.width;

			if (xs<0)
			{
				xs = 0;
			}

			float fTrafDist  = m_lUpLevel-y;
			fTrafDist/=m_lUpLevel;

			y   = rect.y+ fTrafDist*rect.height;

			dc.DrawLine( xs, rect.y+rect.width, xs, y);

			if ( tStart-entry1.t>m_nTimePeriodSec )
				break;
		}
	}
}



//new>old
void CLevelGraph::DrawGraph(class wxBufferedPaintDC& dc, const TRAFFICDATA& data,
							 const wxRect& rect, const wxColor& color)
{
	dc.DestroyClippingRegion();
	dc.SetClippingRegion( rect );

	wxPointList list;
	
	time_t tStart;

	wxPen   pen(color, 2);
	wxBrush br(color);

	dc.SetPen(pen);
	dc.SetBrush(br);

	if ( data.size()>=2)
	{
		tStart = data.begin()->t;

		TRAFFICDATA::const_iterator iTmp;

		for ( TRAFFICDATA::const_iterator iCur = data.begin();
				iCur!=data.end();++iCur )
		{
			const TRAFFICDATAENTRY& entry1 = *iCur;
						
			int x, y;

			x = entry1.t;
			y = entry1.data;

			float fTimeDist  = tStart-x;

			fTimeDist/=m_nTimePeriodSec;

			int xs  =rect.x+rect.width- fTimeDist*rect.width;

			if (xs<0)
			{
				xs = 0;
			}

			float fTrafDist  = m_lUpLevel-y;
			
			fTrafDist/=m_lUpLevel;
	
			y   = rect.y+ fTrafDist*rect.height; 

			list.Append( new wxPoint(xs,y) );

			if ( tStart-entry1.t>m_nTimePeriodSec )
				break;
		}

		if ( list.size()>2 )
		{
			dc.DrawSpline( &list  );
		}

		wxPointList::iterator iCur=list.begin();
		for (iCur=list.begin();iCur!=list.end();iCur++)
		{
			delete (wxPoint*)*iCur;
			*iCur = NULL;
		}
	}
}


////////////////////////////////////////////////////////////////////////////////////////////
//UTL
bool CLevelGraph::GetDistance(const TRAFFICDATAENTRY& entry1, const TRAFFICDATAENTRY& entry2, int& x, int& y)
{
	bool bRes(false);

	y=0;
	x=0;

	if ( entry1.data >= entry2.data )
	{
		time_t tDelta1 = entry1.t-entry2.t;
		unsigned long lValue1 = entry1.data - entry2.data;
		if ( tDelta1 ) lValue1/=tDelta1;

		y = (int)lValue1;
		x = (int)entry1.t;

		bRes = true;
	}

	return bRes;
}

unsigned int CLevelGraph::CalculateUpLevel( unsigned int lDataMax )
{
	unsigned long lData=GetScale(lDataMax);
	lData=lData<INIT_Y_VAL_MAX?INIT_Y_VAL_MAX:lData;

	while (lDataMax/lData>10) 
	{
		lData*=10;
	} 

	unsigned int lMaxFac = (lDataMax)/lData;
	unsigned int lMax = (lMaxFac+1)*lData;

	return lMax;
}


unsigned long CLevelGraph::GetScale(unsigned long lMax)
{
	unsigned long lScale(1);
	
	if ( lMax <  (1L << 10) )
	{
		lScale =  1L << 10;
	}
	else if ( lMax <  (1L << 20) )
	{
		lScale =  1L << 20;
	}
	else if ( lMax <  (1L << 30) )
	{
		lScale =  1L << 30;
	}
		
	return lScale/1024;
}

wxString CLevelGraph::FormatLabel( unsigned long lVal, unsigned long lMax)
{
	if ( lVal )
	{
		const wxString strFormat = _("%3.u %s");
		return  wxString::Format(strFormat, lVal, m_strMesureItemName.c_str());
	}

	return wxT("");
}


////////////////////////////////////////////////////////////////////////////////////////////
// Data Management

//remove old values
void CLevelGraph::RemoveOldValues(TRAFFICDATA& data, time_t tExpired)
{
	TRAFFICDATA::iterator iCur = data.begin();

	for ( ;iCur!=data.end();iCur++ )
	{
		TRAFFICDATAENTRY& entryPrev = *iCur;

		if ( entryPrev.t < tExpired )
			break;
	}

	if ( iCur != data.end() )
		data.erase(iCur, data.end());
}

//new values stored in front
void CLevelGraph::AddData(  TRAFFICDATA& data,
							unsigned long long lVal,
							time_t tEvent )
{
	TRAFFICDATAENTRY entry;
	entry.t 	= tEvent;
	entry.data	= lVal;
	data.push_front( entry );

	QuickCompress( data );

	VerifyData( data );


	wxSize szMe = GetClientSize();
	size_t nMaxPoints = 2*szMe.GetWidth();

	//We have 2x more points that we can draw
	if (data.size()>nMaxPoints ) CompressList( data );
}

unsigned long CLevelGraph::GetMax( const TRAFFICDATA& data )
{
	unsigned long lMax(0);

	if ( cGrowGraphMode==m_cGraphMode ||
		 cGrowBarGraphMode==m_cGraphMode )
	{
		time_t tExp = time(NULL);
		tExp-=m_nTimePeriodSec;
		
		if ( data.size()>=2)
		{
			TRAFFICDATA::const_iterator iTmp;

			for ( TRAFFICDATA::const_iterator iCur = data.begin();
					iCur!=data.end();iCur++ )
			{
				iTmp = iCur;
				const TRAFFICDATAENTRY& entryNew = *iTmp;
				iTmp++; if (iTmp==data.end()) break;
				const TRAFFICDATAENTRY& entryOld = *iTmp;

				//Do not count expired values
				if ( entryOld.t < tExp  )
					break;

				if (!entryOld.data || !entryNew.data )
					continue;
				
				time_t tDelta = entryNew.t-entryOld.t;
				unsigned long lValue = 0;
				
				if ( entryNew.data > entryOld.data )
					lValue = entryNew.data - entryOld.data;
				
				if ( tDelta ) lValue/=tDelta;

				if ( lValue>lMax) lMax=lValue;
			}
		}
	}
	else
	{
		for ( TRAFFICDATA::const_iterator iCur = data.begin();
		  	  iCur!=data.end();iCur++ )
		{
			const TRAFFICDATAENTRY& entryNew = *iCur;

			if ( entryNew.data>lMax )
			{
				lMax = entryNew.data;
			}
		}
	}

	return lMax;
}

void CLevelGraph::SetTimePeriod(int nPer)
{
	m_nTimePeriodSec = nPer;
	Refresh();
}

#define MAX_DIFF	100
#define AVG_BLOCK	100

void CLevelGraph::VerifyData( TRAFFICDATA& data )
{
	if ( data.size()>=2)
	{
		TRAFFICDATA::iterator iTmp;

		unsigned long long lEvg(0);
		unsigned long long lEvgTmp(0);
		size_t tBlock = 0;
		bool bFirst(true);

		for ( TRAFFICDATA::iterator iCur = data.begin();
			iCur!=data.end();iCur++ )
		{
			const TRAFFICDATAENTRY& entryNew = *iCur;
			lEvgTmp+=entryNew.data;

			//skip 0
			if (!entryNew.data) continue;

			if ( ++tBlock%AVG_BLOCK == 0 )
			{
				lEvg+=lEvgTmp/AVG_BLOCK;
				if ( !bFirst )
				{
					lEvg/=2;
				}

				bFirst  = false;
				tBlock  = 0;
				lEvgTmp = 0;
			}
		}

		/*
		if ( tBlock )
		{
			lEvg+=lEvgTmp/tBlock;

			if (!bFirst)
			{
				lEvg/=2;
			}
		}*/

		if ( lEvg )
		{
			try
			{
				for ( TRAFFICDATA::iterator iCur = data.begin();iCur!=data.end();iCur++ )
				{
					iTmp = iCur;
					TRAFFICDATAENTRY& entryNew = *iTmp;

					if ( m_cGraphMode==cNormalGraphMode )
					{
						if ( entryNew.data>lEvg*MAX_DIFF )
						{
							 entryNew.data = lEvg*MAX_DIFF;
							//data.erase(iTmp);
							//iCur = data.begin();
							continue;
						}
					}
					else
					{
						iTmp++; if (iTmp==data.end()) break;
						TRAFFICDATAENTRY& entryOld = *iTmp;

						time_t tDelta = entryNew.t-entryOld.t;
						unsigned long lValue = 0;

						if ( entryNew.data > entryOld.data )
							lValue = entryNew.data - entryOld.data;

						if ( tDelta ) lValue/=tDelta;

						if ( lValue>MAX_DIFF*lEvg )
						{
							data.erase(iTmp);
							iCur = data.begin();
							continue;
						}
					}
				}
			}catch(...) {}
		}
	}
}


void CLevelGraph::QuickCompress( TRAFFICDATA& data )
{
	time_t tNow = time(NULL);

	try
	{
		if (data.size()>50 )
		{
			TRAFFICDATA::iterator iB, iC;
			TRAFFICDATA::iterator iA = data.begin();

			std::advance(iA, 50);

			for (;iA!=data.end();iA++ )
			{
				iB=iA;
				const TRAFFICDATAENTRY& a = *iA;

				//Erase old
				if ( tNow-a.t>24*60*60 )
				{
					data.erase( iA, data.end() );
					break;
				}

				++iB;  if (iB==data.end()) break;
				const TRAFFICDATAENTRY& b = *iB;

				iC = iB;

				++iC;  if (iC==data.end()) break;
				const TRAFFICDATAENTRY& c = *iC;

				//don't need b 
				if ( a.data == b.data &&
					 b.data == c.data )
				{
					data.erase( iB );
					break;
				}
			}
		}
	}
	catch(...) {}
}

void CLevelGraph::CompressList( TRAFFICDATA& data )
{
	time_t tNow = time(NULL);

	try
	{
		TRAFFICDATA dataNew;

		TRAFFICDATA::iterator iCur   = data.begin();
		TRAFFICDATA::iterator iStart = data.end();

		//Keep one hour of actual data
		for ( ;iCur!=iStart;iCur++ )
		{
			if ( tNow-iCur->t>60*60 )
			{
				break;
			}

			dataNew.push_back(*iCur);
		}

		for ( ;iCur!=data.end();iCur++ )
		{
			TRAFFICDATAENTRY a = *iCur;

			//get next point
			iCur++;

			//Last point
			if (iCur==data.end())
			{
				dataNew.push_back(a);
				break;
			}
			else
			{
				const TRAFFICDATAENTRY& b = *iCur;

				if (!a.bCompressed && !b.bCompressed )
				{
					a.data = (a.data+b.data)/2;
					a.t    = (a.t+b.t)/2;
					a.bCompressed = true;
				}
			}

			dataNew.push_back(a);
		}

		data = dataNew;
	}
	catch(...)
	{
	}
}

const CLevelGraph::TRAFFICDATA& CLevelGraph::GetData(
	const wxString& strName )
{
	static TRAFFICDATA emptyData;

	TRAFFICDATASTORAGE::const_iterator iCur = m_lstGraphData.find( strName );

	if ( iCur==m_lstGraphData.end() )
		return emptyData;
	else
		return iCur->second;
}

unsigned long  CLevelGraph::GetMaxDataVal()
{
	unsigned long lRes(0);

	for( TRAFFICDATASTORAGE::const_iterator iCur = m_lstGraphData.begin();
		iCur != m_lstGraphData.end();++iCur )
	{
		unsigned long lVal = GetMax( iCur->second );

		if (lVal>lRes)
		{
			lRes = lVal;
		}
	}

	return lRes;
}


void  CLevelGraph::Reset()
{
	time_t tNow = time(NULL);

	for (TRAFFICDATASTORAGE::const_iterator iCur = m_lstGraphData.begin();
		iCur!=m_lstGraphData.end();++iCur)
	{
		SetData( iCur->first, 0, tNow );
	}
}

unsigned long long CLevelGraph::GetCurVal(const wxString& strName, bool& bEmpty)
{
	unsigned long long lVal = 0;

	TRAFFICDATASTORAGE::iterator iCur = m_lstGraphData.find(strName);

	bEmpty = true;

	if ( iCur != m_lstGraphData.end() )
	{
		TRAFFICDATA& data = iCur->second;

		bEmpty = false;

		if (cNormalGraphMode == m_cGraphMode )
		{
			if ( !data.empty() )	lVal = data.front().data;
		}
		else
		{
			if ( data.size()>=3)
			{
				TRAFFICDATA::const_iterator iTmp = data.begin();
				const TRAFFICDATAENTRY& entry1 = *iTmp;
				iTmp++;
				const TRAFFICDATAENTRY& entry2 = *iTmp;

				int x, y;

				if ( GetDistance( entry1, entry2, x,  y  ) )
				{
					lVal = y;
				}
			}
		}
	}

	return lVal;
}
